import { uploadFile, type UploadResult } from '@/api/upload'
import { useAppStore } from '@/stores'
import { type ImgCropOptions, useImgCropContext } from '@/components/img-crop-modal'

class ErrorResult extends Error {
    constructor(public file: File, message?: string) {
        super(message)
    }
}
interface UseUploadOpts {
    receiveResults?: (result: UploadResult[], errors: ErrorResult[]) => void
    tick?: (resultOrErr: UploadResult | ErrorResult, isError: (val: unknown) => val is ErrorResult) => void
    errorHandler?: (e: unknown) => any
    finallyHandler?: () => any
    /**
     * 上传类型
     * @default 'image'
     */
    type?: Parameters<typeof triggerFile>['0']
    multipleOrOpts?: Parameters<typeof triggerFile>['1']
    /**
    * @default true
    */
    globalLoading?: boolean
    pLimitOptions?: Parameters<typeof pLimit>['1']
    beforeUpload?: (files: File[]) => IAwaitable<boolean | undefined>
    /** @default false */
    crop?: boolean
    cropOpts?: MaybeRefOrGetter<Omit<ImgCropOptions, 'img' | 'success'>>
}
interface UploadActionOpts extends Pick<UseUploadOpts, "type" | "multipleOrOpts"> {
    files?: File[]
}
export function useUpload(opts: UseUploadOpts) {
    const uploading = ref<boolean>(false)
    const imgCropContext = useImgCropContext()
    const appStore = useAppStore()
    opts.globalLoading ??= true
    opts.crop ??= false
    async function uploadAction(_opts?: UploadActionOpts) {
        const results: UploadResult[] = []
        const errors: ErrorResult[] = []
        let finalFnCalled = false
        let skip = false
        try {
            const type = typeof _opts?.type === 'undefined' ? opts.type : _opts.type
            const multipleOrOpts = _opts?.multipleOrOpts ?? opts.multipleOrOpts
            const files = _opts?.files ?? await triggerFile(typeof type === "undefined" ? 'image' : type, multipleOrOpts)
            if (typeof opts.beforeUpload === 'function') {
                if ((await opts.beforeUpload(files)) === false) {
                    skip = true
                    return
                }
            }
            let finished = 0
            let multiple = false
            if (typeof multipleOrOpts === "boolean") {
                multiple = multipleOrOpts
            } else if (typeof multipleOrOpts?.multiple === "boolean") {
                multiple = multipleOrOpts.multiple
            }
            if (!multiple && opts.crop && imgCropContext && imgCropContext.imgCropModalRef.value) {
                imgCropContext.imgCropModalRef.value.openModal({
                    img: files[0],
                    success(file) {
                        upload(file, 0)
                    },
                    ...toValue(opts.cropOpts),
                })
                return
            }
            pLimit(files.map((file, i) => () => upload(file, i)), opts.pLimitOptions)
            async function upload(file: File, index: number) {
                openLoading()
                try {
                    results[index] = await uploadFile(file)
                } catch (e) {
                    errors[index] = new ErrorResult(file, (e as Error).message ?? e)
                } finally {
                    finished++
                    opts.tick?.(results[index] ?? errors[index], isError)
                    if (finished === files.length) {
                        closeLoading()
                        opts.receiveResults?.(filter(results), filter(errors))
                        callFinalHandler()
                    }
                }
            }
        }
        catch (e) {
            opts.errorHandler?.(e)
        }
        finally {
            skip && callFinalHandler()
        }
        function callFinalHandler() {
            if (finalFnCalled === false) {
                opts.finallyHandler?.()
                finalFnCalled = true
            }
        }
    }
    function isError(val: unknown): val is ErrorResult {
        return val instanceof ErrorResult
    }
    function filter<T>(list: Array<T>): Array<T> {
        return list.filter(l => isSetValue(l))
    }
    function openLoading() {
        if (opts.globalLoading && !uploading.value) {
            appStore.toggleLoading(true, '上传中...')
        }
        uploading.value = true
    }
    function closeLoading() {
        if (opts.globalLoading && uploading.value) {
            appStore.toggleLoading(false)
        }
        uploading.value = false
    }
    return {
        uploadAction,
        uploading,
    }
}
